/**
 * 已知贝塞尔曲线的四个控制点，根据曲线长度的比例求对应长度的(X,Y)坐标
 * points: Array 贝塞尔曲线的四个控制点坐标
 * targetLength: Number 曲线长度的比例
 */
export function bezierCurveXYByLength (points, t) {
  const mt = 1 - t
  const mt2 = mt * mt
  const mt3 = mt2 * mt
  const t2 = t * t
  const t3 = t2 * t

  const x = mt3 * points[0] + 3 * mt2 * t * points[2] + 3 * mt * t2 * points[4] + t3 * points[6]
  const y = mt3 * points[1] + 3 * mt2 * t * points[3] + 3 * mt * t2 * points[5] + t3 * points[7]

  return { x, y }
}

/**
 * 已知贝塞尔曲线的四个控制点，根据曲线长度的比例求对应长度的点与曲线的切线的角度
 * points: Array 贝塞尔曲线的四个控制点坐标
 * targetLength: Number 曲线长度的比例
 */
export function bezierCurveAngle (points, t) {
  const mt = 1 - t
  const mt2 = mt * mt
  const t2 = t * t

  const dx = -3 * points[0] * mt2 + 3 * points[2] * (mt2 - 2 * mt * t) + 3 * points[4] * (2 * mt * t - t2) + 3 *
    points[6] * t2
  const dy = -3 * points[1] * mt2 + 3 * points[3] * (mt2 - 2 * mt * t) + 3 * points[5] * (2 * mt * t - t2) + 3 *
    points[7] * t2
  return Math.atan2(dy, dx) * (180 / Math.PI)
}

/**
 * 已知贝塞尔曲线的四个控制点，根据曲线长度
 * points: Array 贝塞尔曲线的四个控制点坐标
 * n: Number 曲线的分割段数 数字越大，长度越精确，但计算量也越大，建议50～100
 */
export function bezierCurveLength (points, n) {
  let length = 0
  const delta_t = 1 / n
  for (let i = 0; i < n; i++) {
    const t1 = i * delta_t, t2 = (i + 1) * delta_t
    const B_t1 = bezierCurveXYByLength(points, t1)
    const B_t2 = bezierCurveXYByLength(points, t2)
    const segmentLength = euclideanDistance(B_t1, B_t2)
    length += segmentLength
  }
  return length
}

/**
 * 已知贝塞尔曲线的四个控制点，根据目标点的Y坐标，求目标点X坐标
 * points: Array 贝塞尔曲线的四个控制点坐标
 * targetY: Number 目标点的Y坐标
 * epsilon: Number 误差
 */
export function findIntersectionX (points, targetY, epsilon = 0.001) {
  let t = 0, x, y, prevY = points[1]
  const tStep = 0.01

  while (t <= 1) {
    x = bezierCurveX(t, points[0], points[2], points[4], points[6])
    y = bezierCurveY(t, points[1], points[3], points[5], points[7])

    if ((prevY - targetY) * (y - targetY) <= 0) {
      let dy = tStep / 2

      while (dy > epsilon) {
        if ((prevY - targetY) * (y - targetY) <= 0) t -= dy
        else t += dy

        dy /= 2

        x = bezierCurveX(t, points[0], points[2], points[4], points[6])
        y = bezierCurveY(t, points[1], points[3], points[5], points[7])
      }

      return x
    }

    t += tStep
    prevY = y
  }

  return null // 如果未找到相交点，则返回 null
}

/**
 * 已知贝塞尔曲线的四个控制点，根据目标点的X坐标，求目标点Y坐标
 * points: Array 贝塞尔曲线的四个控制点坐标
 * targetX: Number 目标点的X坐标
 * epsilon: Number 误差
 */
export function findIntersectionY (points, targetX, epsilon = 0.001) {
  let t = 0, x, y, prevX = points[0]
  const tStep = 0.01

  while (t <= 1) {
    x = bezierCurveX(t, points[0], points[2], points[4], points[6])
    y = bezierCurveY(t, points[1], points[3], points[5], points[7])

    if ((prevX - targetX) * (x - targetX) <= 0) {
      let dx = tStep / 2

      while (dx > epsilon) {
        if ((prevX - targetX) * (x - targetX) <= 0) t -= dx
        else t += dx

        dx /= 2

        x = bezierCurveX(t, points[0], points[2], points[4], points[6])
        y = bezierCurveY(t, points[1], points[3], points[5], points[7])
      }

      return y
    }

    t += tStep
    prevX = x
  }

  return null // 如果未找到相交点，则返回 null
}

// 贝塞尔曲线的 x(t) 方程
function bezierCurveX (t, startX, cX1, cX2, endX) {
  const t2 = Math.pow(t, 2)
  const t3 = Math.pow(t, 3)
  return startX * Math.pow(1 - t, 3) + cX1 * 3 * t * Math.pow(1 - t, 2) + cX2 * 3 * t2 * (1 - t) + endX * t3
}

// 贝塞尔曲线的 y(t) 方程
function bezierCurveY (t, startY, cY1, cY2, endY) {
  const t2 = Math.pow(t, 2)
  const t3 = Math.pow(t, 3)
  return startY * Math.pow(1 - t, 3) + cY1 * 3 * t * Math.pow(1 - t, 2) + cY2 * 3 * t2 * (1 - t) + endY * t3
}

function euclideanDistance (control1, control2) {
  const dx = control2.x - control1.x
  const dy = control2.y - control1.y
  return Math.sqrt(dx * dx + dy * dy)
}